import re
from typing import Iterable, Literal, Union, TYPE_CHECKING
from MelodieFuncFlow import MelodieGenerator
from win32com.client import constants
from math import isclose
from ..models import *
from .inline_shape_extractor import extract_inline_shape_info
from pywintypes import com_error

if TYPE_CHECKING:
    from ..app.app import AppStatus


class Context:
    def __init__(self, status: "AppStatus") -> None:
        self.status = status

    def info(self, msg):
        if self.status:
            self.status.log("info", msg)
        else:
            print(msg)

    def violation(self, msg):
        if self.status:
            self.status.log("violation", msg)
        else:
            print(msg)


context = Context(None)


def extract_special_indentations(
    paragraph: "Paragraph",
) -> SpecialIndentationFormatInfo:
    """获取特殊缩进：首行缩进和悬挂缩进"""
    if not isclose(paragraph.CharacterUnitFirstLineIndent, 0.0):
        return SpecialIndentationFormatInfo.from_indentation_value(
            paragraph.CharacterUnitFirstLineIndent, "char"
        )

    elif not isclose(paragraph.FirstLineIndent, 0.0):
        return SpecialIndentationFormatInfo.from_indentation_value(
            paragraph.FirstLineIndent, "pt"
        )
    else:
        return None


def extract_paragraph_lr_indentations(
    paragraph: "Paragraph", side=Literal["Right", "Left"]
) -> Distance:
    indent_chars = getattr(paragraph, f"CharacterUnit{side}Indent")
    if not isclose(indent_chars, 0.0):
        return Distance(indent_chars, "char")
    indent_pts = getattr(paragraph, f"{side}Indent")
    if not isclose(indent_pts, 0.0):
        return Distance(indent_pts, "pt")
    return Distance(0.0, "pt")


def extract_paragraph_indentations(paragraph: "Paragraph") -> IndentationInfo:
    """获取自然段的缩进"""
    # indent_type = lambda val: "first_line" if val > 0 else "hanging"
    # first_line_indentation: IndentationInfo

    first_line_indentation = IndentationInfo(
        special=extract_special_indentations(paragraph),
        left=extract_paragraph_lr_indentations(paragraph, "Left"),
        right=extract_paragraph_lr_indentations(paragraph, "Right"),
    )
    return first_line_indentation


def char_type(text) -> Literal["cn", "western"]:
    regex = "[(\u4e00-\u9fa5)(\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3010|\u3011|\u007e)]+"
    if re.search(regex, text):
        return "cn"
    else:
        return "western"


def extract_paragraph_info(paragraph: "Paragraph") -> ParagraphInfo:
    para_range = paragraph.Range
    font = para_range.Font
    # if font.Name == "":
    fontInfo = set()
    for character in para_range.Characters:
        text = character.Text
        if text.strip() == "":
            continue
        print(char_type(text), text)
        fontInfo.add(
            (
                char_type(text),
                character.Font.Name,
                int(character.Font.Size * 10),
                bool(character.Bold),
                bool(character.Italic),
            )
        )
    fontInfo = [
        FontInfo(ch_type, name, FontSize(size / 10.0), bold, italic)
        for ch_type, name, size, bold, italic in fontInfo
    ]

    # else:
    #     fontInfo = [
    #         FontInfo(
    #             char_type(para_range.Text),
    #             font.Name,
    #             FontSize(font.Size),
    #             bool(font.Bold),
    #             bool(font.Italic),
    #         )
    #     ]
    text: str = para_range.Text
    inline_shapes_info = (
        MelodieGenerator(para_range.InlineShapes).map(extract_inline_shape_info).l
    )
    start_page = para_range.Characters.First.Information(3)
    end_page = para_range.Characters.Last.Information(3)
    first_char_height = para_range.Characters.First.Information(
        constants.wdVerticalPositionRelativeToPage
    )
    last_char_height = para_range.Characters.First.Information(
        constants.wdVerticalPositionRelativeToPage
    )

    # if text.startswith((" ", "\t")):
    #     context.violation("自然段不要以空格开头，请尽量使用缩进、对齐等格式进行调节")

    # context.info(
    #     f"正在解析自然段 “{text[:10]+'...' if len(text)>=10 else text}”，位于第{start_page}页"
    # )

    return ParagraphInfo(
        indentations=extract_paragraph_indentations(paragraph),
        font=fontInfo,
        text=text,
        style=StyleInfo(paragraph.Style.NameLocal),
        inline_shapes=inline_shapes_info,
        page_range=PageRangeInfo(start_page, end_page),
        position=Position(
            0,
            first_char_height,
            0,
            last_char_height - first_char_height,
        ),
    )


def extract_element_info(
    element: Union["Paragraph", "Table"]
) -> Union[ParagraphInfo, None]:
    cls_name = element.__class__.__name__
    if cls_name == "Paragraph":
        return extract_paragraph_info(element)
    elif cls_name == "Table":
        print("found table!")
        return None


def extract_elements(section_range: "Range") -> list:
    elements = []

    context.info("正在提取所有段落位置")

    # 获取段落
    for paragraph in section_range.Paragraphs:
        try:
            if not paragraph.Range.Information(constants.wdWithInTable):
                elements.append(paragraph)
        except com_error:
            pass
    context.info("正在提取所有表格位置")

    # 获取表格
    for table in section_range.Tables:
        elements.append(table)

    # 按照文档中出现的顺序排序
    elements.sort(key=lambda x: x.Range.Start)
    context.info("已提取段落及表格的位置信息")

    return elements


def extract_section_info(section: "Section") -> SectionInfo:
    sectionRange: "Range" = section.Range
    # total_paragraphs =
    startPage = sectionRange.Paragraphs.First.Range.Information(3)
    endPage = sectionRange.Paragraphs.Last.Range.Characters.Last.Information(3)
    # try:
    sectionPageSetup = section.PageSetup
    # except com_error:
    #     sectionPageSetup = None
    context.info("开始抽取当前章节的文档元素...")
    elements = extract_elements(sectionRange)
    context.info("当前章节的文档元素抽取完毕...")
    paragraphs_info = (
        MelodieGenerator(elements)
        .indexed_extra_job(
            lambda i, _: context.info(f"正在分析第 {i+1}/{len(elements)} 个元素...")
        )
        .map(extract_element_info)
        .l
    )
    return SectionInfo(
        sectionNumber=sectionRange.Information(2),
        margin=MarginInfo.fromPageSetup(sectionPageSetup)
        if sectionPageSetup is not None
        else None,
        range=PageRangeInfo(startPage=startPage, endPage=endPage),
        paragraphs=paragraphs_info,
    )


def extract_doc_full_info(doc: "Document") -> DocumentInfo:
    # 获取文档中Section的数量
    total_sections: int = doc.Sections.Count
    context.info(f"文档共有 {total_sections} 节")

    def _(section):
        # try:
        return extract_section_info(section)
        # except com_error:
        #     return None

    sections_info = (
        MelodieGenerator((doc.Sections(i) for i in range(1, total_sections + 1)))
        .indexed_extra_job(
            lambda i, _: context.info(f"正在分析第 {i+1}/{total_sections} 节...")
        )
        .map(_)
        .filter(lambda item: item is not None)
        .l
    )
    return DocumentInfo(sections_info)
